// Copyright (c) 2012, http://code.google.com/p/example-xplane-plugins/
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//     * Neither the name of "example-xplane-plugins" nor the
//       names of its contributors may be used to endorse or promote products
//       derived from this software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL "example-xplane-plugins" BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


/*------------------------------------------------------------------------*/
// INSTRUCTIONS - GENERAL USAGE
/*------------------------------------------------------------------------*/

// USAGE: (XPLANE)
// ENABLE plugin to CREATE the shared memory interface.
// DISABLE plugin to DESTROY the shared memory interface.

// USAGE: (MATLAB and OCTAVE)
// CONNECT to an EXISTING shared memory interface using XPIMInit()
// CAPTURE an image using XPIMCaptureImage()
// DISCONNECT from the shared memory interface using XPIMCleanup()


/*------------------------------------------------------------------------*/
// INCLUDES
/*------------------------------------------------------------------------*/

#ifdef WIN32
// Can't use strcpy_s for copying to XPlane buffers of unknown length
#pragma warning(disable:4996)
// Required for OpenGL on Windows
#include <windows.h>
#endif

// OpenGL - for glReadPixels
#include <gl/gl.h>

// Boost - for shared memory comms
#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/interprocess/sync/named_mutex.hpp>

// XPlane - for drawing callback
#include "XPLMDisplay.h"
#include "XPLMUtilities.h"

// Plugin - shared constants 
#include "XPScreenGrabConstants.h"

/*------------------------------------------------------------------------*/
// NAMESPACES
/*------------------------------------------------------------------------*/

using namespace boost::interprocess;

/*------------------------------------------------------------------------*/
// STATIC CONSTANTS AND VARIABLES
/*------------------------------------------------------------------------*/

static bool s_pluginStarted = false;

static mapped_region* s_region = NULL;
static named_mutex*   s_mutex = NULL;
static char error_buffer[MAX_ERROR_STRING_LENGTH] = {0};

/*------------------------------------------------------------------------*/
// FILE SCOPE FUNCTION DECLARATIONS
/*------------------------------------------------------------------------*/

// Create the shared memory object and mutex
static void CreateSharedState();
// Delete the shared memory object and mutex
static void DestroySharedState();
// Force XPlane window to match capture settings
static void EnforceWindowSize();

// Handle any graphics updates
int GraphicsCallBack(XPLMDrawingPhase     inPhase,    
                     int                  inIsBefore,    
                     void *               inRefcon);

/*------------------------------------------------------------------------*/
// IMPLEMENTATION
/*------------------------------------------------------------------------*/

PLUGIN_API int XPluginStart(char *outName,
                            char *outSig,
                            char *outDesc)
{
  if (!s_pluginStarted)
  {
    // Used by widget to make sure only one widgets instance created
    s_pluginStarted = true;

    // Can't use strcpy_s - overwites the string memory with 0xFD debug bytes before string copying
    // I would need to know char array lengths in order to use it safely
    strcpy(outName, PLUGIN_NAME);
    strcpy(outSig,  PLUGIN_ID);
    strcpy(outDesc, PLUGIN_DESCRIPTION);
  }

  // Always return 1 to indicate we have handled this DLL call.
  // Really random things happen if you return 0 and carry on. (Other plugins get called instead etc.)
  return 1;
}

/*------------------------------------------------------------------------*/

PLUGIN_API void	XPluginStop(void)
{
  if (s_pluginStarted)
  {
    s_pluginStarted = false;
  }
}

/*------------------------------------------------------------------------*/

PLUGIN_API int XPluginEnable(void)
{
    // Maybe Create and Destroy the shared state here?)
  DestroySharedState();
  CreateSharedState();

  // The function "GraphicsCallBack" will be called each frame
  XPLMRegisterDrawCallback(
    GraphicsCallBack,
    xplm_Phase_LastCockpit,
    0,
    NULL);

  // Always return 1 to indicate that we have handled this function
  return 1;
}

/*------------------------------------------------------------------------*/

PLUGIN_API void XPluginDisable(void)
{
  // Stops XPlane from calling the "callback"
  XPLMUnregisterDrawCallback(
    GraphicsCallBack,
    xplm_Phase_LastCockpit,
    0,
    NULL);

  // Destroy shared memory object
  DestroySharedState();
}

/*------------------------------------------------------------------------*/

PLUGIN_API void XPluginReceiveMessage(XPLMPluginID inFrom, long inMsg, void * inParam)
{
  // Do nothing - no known plugin interactions as of yet
}

/*------------------------------------------------------------------------*/

void CreateSharedState()
{
  try
  {
    // Open or create the named mutex
    s_mutex = new named_mutex(create_only, SHARED_MUTEX_NAME);
  }
  catch (interprocess_exception& err)
  {
    sprintf(error_buffer, "Exception named_mutex = %s\n", err.what());
    XPLMDebugString(error_buffer);
  }
  catch (...)
  {
    sprintf(error_buffer, "Exception named_mutex = %s\n", "UNKNOWN");
    XPLMDebugString(error_buffer);
  }

  try
  {
    // Create shared memory segment
    shared_memory_object shm_obj(
      create_only,                 // only create
      SHARED_MEMORY_NAME,          // name
      read_write);                 // read-write mode

    shm_obj.truncate(SCREENSHOT_NUMBYTES);

    // Map the whole shared memory in this process
    s_region = new mapped_region(shm_obj, read_write);
  }
  catch (interprocess_exception& err)
  {
    sprintf(error_buffer, "Exception shared_memory_object = %s\n", err.what());
    XPLMDebugString(error_buffer);
  }
  catch (...)
  {
    sprintf(error_buffer, "Exception shared_memory_object = %s\n", "UNKNOWN");
    XPLMDebugString(error_buffer);
  }
}

void DestroySharedState()
{
  // Delete the mutex
  try
  {
    named_mutex::remove(SHARED_MUTEX_NAME);

    delete s_mutex;
    s_mutex = 0;
  }
  catch (interprocess_exception& err)
  {
    sprintf(error_buffer, "Exception named_mutex::remove = %s\n", err.what());
    XPLMDebugString(error_buffer);
  }
  catch (...)
  {
    sprintf(error_buffer, "Exception named_mutex::remove = %s\n", "UNKNOWN");
    XPLMDebugString(error_buffer);
  }

  // Remove shared memory segment
  try
  {
    delete s_region;
    s_region = NULL;

    shared_memory_object::remove(SHARED_MEMORY_NAME);
  }
  catch (interprocess_exception& err)
  {
    sprintf(error_buffer, "Exception shared_memory_object::remove = %s\n", err.what());
    XPLMDebugString(error_buffer);
  }
  catch (...)
  {
    sprintf(error_buffer, "Exception shared_memory_object::remove = %s\n", "UNKNOWN");
    XPLMDebugString(error_buffer);
  }
}

// GFX LOOP
int GraphicsCallBack(XPLMDrawingPhase     inPhase,    
                     int                  inIsBefore,    
                     void *               inRefcon)
{
  ///////////////////////////////////
  // Enforcing particular screen size
  ///////////////////////////////////
  EnforceWindowSize();
  ///////////////////////////////////

  ///////////////////////////////////
  // Performing screen capture
  ///////////////////////////////////
  if (s_mutex->try_lock())
  {
    if (NULL!=s_region)
    {
      GLvoid* pPixelData(reinterpret_cast<GLvoid*>(s_region->get_address()));
      if (NULL!=pPixelData)
      {
        // Get pixels write directly to memory mapped section
        glReadPixels(0,0, SCREENSHOT_WIDTH, SCREENSHOT_HEIGHT, GL_RGB, GL_UNSIGNED_BYTE, pPixelData);
      }
    }
    s_mutex->unlock();
  }
  ///////////////////////////////////

  return 1;  // Let XPlane draw the frame
}

void EnforceWindowSize()
{
  int width(0);
  int height(0);
  XPLMGetScreenSize(&width, &height);

  // If different from capture size
  if ((SCREENSHOT_WIDTH!=width)||(SCREENSHOT_HEIGHT!=height))
  {
#if defined(IBM)
    HWND h(FindWindow(NULL, L"X-System"));
    if (NULL!=h)
    {
      SetWindowPos(h, NULL, 0, 0, SCREENSHOT_WIDTH, SCREENSHOT_HEIGHT, SWP_NOMOVE);
    }
#elif defined(APL)
#error "TODO: Force screen size to capture size"
#elif defined(LIN)
#error "TODO: Force screen size to capture size"
#endif
  }
}

/*------------------------------------------------------------------------*/
